package controller;

import hci.ChatGui;

import java.io.File;
import java.util.ArrayList;

import observerNotification.ChangingStateObserverNotification;
import observerNotification.ErrorObserverNotification;
import observerNotification.ObserverNotification;
import observerNotification.QuestionObserverNotification;
import cs.remoteapp.FileAnswer;
import cs.remoteapp.FileRequest;
import cs.remoteapp.Goodbye;
import cs.remoteapp.Hello;
import cs.remoteapp.TextMessage;
import main.ChatSystem;
import model.Datagram;
import model.Model;
import view.ViewController;


/**
 * Controller in MVC pattern.
 * 
 * <p>
 * <ul>
 * <li>Project: 	ChatSystemG3</li>
 * <li>Called by: 	view.*</li>
 * </ul>
 * </p>
 * 
 * @author COMBES Nicolas - LECHANTRE Arnaud
 * @see AbstractController
 */
public class Controller extends AbstractController{
	
	/**
	 * List containing the message received that the controller have not processed
	 */
	private ArrayList<Datagram> receivedMessages; 
	
	/**
	 * Model of the application
	 */
	private Model model;
	
	/**
	 * State of the application (can be connected, disconnected, sendingFile, Receiving file)
	 */
	private State state;	

	public static enum EnumEndTransfer {RECEIVED, SENT, ABORT};
	
	/**
	 * construct
	 * <ul><li>By default, the App starts in disconnected state.</li></ul>
	 * @param v: is added to the observers list
	 */
	public Controller(ViewController v) {
		super(v);
		this.state = new DisconnectedState();
		this.receivedMessages = new ArrayList<Datagram>();
		this.model= new Model(v);
		if(ChatSystem.debugMode)System.out.println("Controller - Controller : ready to start");
	}
	

	/**
	 * Asks state if connecting is allowed, checks if login is ok
	 * <ul>
	 *  <li>If unauthorized: nothing to do</li>
	 *  <li>If not: transmit error</li>
	 *  <li>If yes: requests the model create a connection</li>
	 * </ul>
	 * 
	 * @param login: login that the user choose. The user IP@ would be added a the end
	 */
	public void performConnect(String login) {
		//First we let the state make his job
		ObserverNotification res=this.state.performConnect(login);
		
		//Then we relay the informations 
		if(res instanceof ErrorObserverNotification){
			//The parameters are uncorrect: login should be more than 4 letters so we generate an error
			//and notify the GUI 
			this.setChanged();
			this.notifyObservers(new Error("Login should be at least 4 letters"));
			this.clearChanged();
		}else if (res instanceof ChangingStateObserverNotification){
			this.state= new ConnectedState();
			this.model.connect(((ChangingStateObserverNotification)res));
		}
	}
	
	/**
	 * Asks state if disconnecting is allowed
	 * <ul>
	 *  <li>If unauthorized: nothing to do</li>
	 *  <li>If not: warns the user</li>
	 *  <li>If yes: request the model to delete the connection</li>
	 * </ul>
	 */
	public void performDisconnect() {
		//First we let the state make his job
		ObserverNotification res=this.state.performDisconnect();
		if(res instanceof ChangingStateObserverNotification){
			this.model.disconnect();
			//Then we relay the informations 
			this.state= new DisconnectedState();
			this.setChanged();
			this.notifyObservers(res);
			this.clearChanged();
		} else if(res instanceof QuestionObserverNotification){
			this.setChanged();
			this.notifyObservers(res);
			this.clearChanged();
		}
	}
	
	
	/**
	 * Asks state if message sending is allowed (i.e ConnectedState) and adds it to the model
	 * <ul>
	 *  <li>If unauthorized: nothing to do</li>
	 *  <li>If not: means message is empty, nothing to do</li>
	 *  <li>If yes: requests the model to send the message and notify the HCI that the message is being sent</li>
	 * </ul>
	 * 
	 * @param convID: unique identifier of the conversation
	 * @param message: content of the message that the user wanna send
	 */
	public void sendMessage(int convId, String message) {
		//able to call the function, params are correct?
		if(this.state.sendMessage(convId, message)){
			//function can be called and params are ok
			//=> adds the message to the model
			this.model.addMessage(convId, message, null);
		}
	}
	
	/**
	 * Asks state if message sending is allowed (i.e connected) and if file path is ok
	 * <ul>
	 *  <li>If unauthorized: nothing to do</li>
	 *  <li>If not: informs the user the possible reasons</li>
	 *  <li>If yes: requests the model to send the file</li>
	 * </ul>
	 *
	 * @param convID: unique identifier of the conversation
	 * @param file: file to send
	 */
	public void sendFile(int convId, File file) {
		Boolean res= this.state.sendFile(file);
			
		//Then we check the answer
		if(res==null){
			//nothing to do, method should no be callable
		}else if (!res){
			//No user selected, so we alert the gui
			this.setChanged();
			this.notifyObservers(new Error("You are not connected or too many file transfers are being performed."));
			this.clearChanged();
		}else{
			//We are authorized to call the function and the parrams are correct
			//So we ask the model to create the conversation
			this.model.addFileMessage(convId, file, null);
		}
	}
	
	/**
	 * Ask the state if creating a conversation is ok (i.e conencted) and if the userList is valid
	 * <ul>
	 *  <li>If unauthorized: nothing to do</li>
	 *  <li>If not: there is no selected users, so notifies the GUI</li>
	 *  <li>If yes: requests the model to create the conversation</li>
	 * </ul>
	 * 
	 * @param userlist: array of usernames to be added to the conv.
	 */
	public void openConversation(String[] userList) {
		//1st we ask the state if the state enable to call the function and if the params are correct
		Boolean res= this.state.openConversation(userList);

		//Then we check the answer
		if(res==null){
			//nothing to do, method should no be callable
		}else if (!res){
			//No user selected, so we alert the gui
			this.setChanged();
			this.notifyObservers(new Error("You should select one users"));
			this.clearChanged();
		}else{
			//We are authorized to call the function and the parrams are correct
			//So we ask the model to create the conversation
			this.model.addConversation(userList, -1);
		}
	}
	
	/**
	 * Ask the state if we can close a conversation (i.e connected)
	 * <ul>
	 *  <li>If unauthorized: nothing to do</li>
	 *  <li>If not: -</li>
	 *  <li>If yes: requests the model to close the conversation</li>
	 * </ul>
	 * 
	 * @param convId: unique identifier of the conversation
	 */
	public void closeConversation(int convID) {
		this.state.closeConversation(convID);
	}
	
	/**
	 * controll
	 * Used to process received messages (listen, check message type, process)
	 */
	//TODO Check if the if is usefull, if not, set deprecated and merge with controllmessage
	public void controll() {
		if (this.receivedMessages.size()>0){
			//A message has been received
			if(ChatSystem.debugMode)System.out.println("Controller - Controller : message received");
			Datagram d= this.receivedMessages.get(0);
			if(d.getMessage() instanceof Hello){
				this.model.addUser(d.getMessage().getUsername()+"@"+d.getRemoteAddress().getHostAddress(), d.getRemoteAddress(), ((Hello)d.getMessage()).isBroadcast());
			} else if (d.getMessage() instanceof Goodbye){
				this.model.removeUser(d.getMessage().getUsername()+"@"+d.getRemoteAddress().getHostAddress(), d.getRemoteAddress());
			} else if (d.getMessage() instanceof TextMessage){
				this.model.addMessage(-1, ((TextMessage)(d.getMessage())).getText(), d.getMessage().getUsername()+"@"+d.getRemoteAddress().getHostAddress());
			} else if (d.getMessage() instanceof FileRequest){
				this.model.addFileRequest(-1, ((FileRequest)(d.getMessage())).getFileName(), ((FileRequest)(d.getMessage())).getFileSize(), d.getMessage().getUsername()+"@"+d.getRemoteAddress().getHostAddress());
			} else if (d.getMessage() instanceof FileAnswer){
				int i=this.state.shareNewFile();
				if(i!=-1){
					if(i==1){
						this.state=new SharingFileState();
					}
					this.model.addFileAnswer(-1, ((FileAnswer)(d.getMessage())).getFileName(), ((FileAnswer)(d.getMessage())).isAccepted(), d.getMessage().getUsername()+"@"+d.getRemoteAddress().getHostAddress());
				} else {
					this.setChanged();
					this.notifyObservers(new Error(ChatGui.strings.getString("errorTooManyTransfert")));
					this.clearChanged();
				}
			}
			this.receivedMessages.remove(0);
		}
	}

	@Override
	public void setMessageReceived(Datagram receivedMess) {
		this.receivedMessages.add(receivedMess);
		super.setMessageReceived(receivedMess);
	}


	@Override
	public void run() {
		if(ChatSystem.debugMode)System.out.println("Controller - Controller : Controller Run");
		//While the user does not ask to close the application
		while(!super.getStopAsked()){
			//we check the messages
			this.controll();
		}
		if(ChatSystem.debugMode)System.out.println("Controller - Controller : Controller stopped");
	}


	@Override
	public void answerFileRequest(int convId, boolean accept, String filename, File donwladLink) {
		//1st we ask the state if the state enable to call the function and if the params are correct
		Boolean res= this.state.answerFileRequest( convId, accept, filename, donwladLink);
		//Then we check the answer
		if(res==null){
			//nothing to do, method should no be callable
		}else if (!res){
			//too many transfert for the moment
			this.setChanged();
			this.notifyObservers(new ErrorObserverNotification(convId, ChatGui.strings.getString("errorTooManyTransfert")));
			this.clearChanged();
		}else{
			if(accept){
				//if the state is accepted and we were not in sending file state, we go to this state
				this.state=new SharingFileState();
			}
			this.model. answerFileRequest(convId, filename, donwladLink, accept);
		}
	}


	@Override
	public void notifyFileReceived(int convId) {
		this.performTransferFinished();
		//Then we inform the model
		this.model.informFileTransferFinished(convId, Controller.EnumEndTransfer.RECEIVED );
	}


	@Override
	public void notifyFileReceptionAbort(int convId) {
		this.performTransferFinished();
		//Then we inform the model
		this.model.informFileTransferFinished(convId, Controller.EnumEndTransfer.ABORT );
	}


	@Override
	public void notifyFileSent(int convId) {
		this.performTransferFinished();
		//Then we inform the model
		this.model.informFileTransferFinished(convId, Controller.EnumEndTransfer.SENT );
	}
	
	/**
	 * Updates the number of transfers being performed and if necessary switch to connected state
	 */
	private void performTransferFinished(){
		//We get the new file transfer number 
		int res=this.state.transferFinished();
				
		//And change the state if necessary
		if(res<1){
			this.state= new ConnectedState();
		}		
	}
	
	@Override
	public void relayError(ErrorObserverNotification error) {
		this.setChanged();
		this.notifyObservers(error);
		this.clearChanged();
	}


	@Override
	public void performConfirm(String action) {
		if(action.equals("disconnect")){
			//If the user has confirmed that he want to disconnect, wo force the disconnection
			this.model.disconnect();
			this.state= new ConnectedState();//in order to be able to force the disconnection we switch to connected state
			ObserverNotification res=this.state.performDisconnect();
			this.state= new DisconnectedState();
			this.setChanged();
			this.notifyObservers(res);
			this.clearChanged();
		}
	}
	
	
	/*--------------------------------------------------
	 * 				DEPRECATED FUNCTIONS
	 *--------------------------------------------------*/
	
	/**
	 * processDatagram
	 * @param datagram: datagram received by the NI interface
	 * @deprecated Not even used, so why bother describing it?
	 */
	public void processDatagram(String datagram) {
		this.state.processDatagram(datagram);
	}
	
	
	/**
	 * addUser
	 * @param name: name of the user to be added
	 * @deprecated
	 */
	public void addUser(String name) {
		this.state.addUser(name);
	}

}
